﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utiles.Observer;
using System.Timers;
using System.Windows.Forms;
using Utiles.Excepcion;
using Utiles.Persistencia;


namespace EntidadesDeNegocio
{
    public class Sistema: Observable
    {    /// <summary>
        /// Instancia privada del singleton
        /// </summary>
        private static Sistema _Instancia;
        /// <summary>
        /// Obejeto para realizar lockeo de hilo
        /// </summary>
        private static object Lockea = new object();
        #region Singleton
        /// <summary>
        /// Instancia de clase (singleton)
        /// </summary>
        public static Sistema Instancia 
        {
            get 
            {
                if (_Instancia == null)
                {
                    lock (Lockea) 
                    {
                        if (_Instancia == null)
                            _Instancia = new Sistema();
                    }
                }
                return _Instancia;
            }
        }
        #endregion
        /// <summary>
        /// Lista de diseños del sistema
        /// </summary>
        public List<Diseno> ListaDisenos
        {
            get
            {
                ServicioPersistencia = new Diseno();              
                return ServicioPersistencia.MostrarTodoBD().Cast<Diseno>().ToList();              
            }
            set{}
        }    
        /// <summary>
        /// Intervalo en segundos para una producción
        /// </summary>
        public int SegundosProduccion {get;set;}
        /// <summary>
        /// Cantidad de robot que se producen por intervalo de produccion
        /// </summary>
        public int CantidadRobotAProducir { get; set; }       
        /// <summary>
        /// Servicio de persistencia
        /// </summary>
        private ObjetoPersistente ServicioPersistencia { get; set; }
        /// <summary>
        /// IdRobotAuto autogenerado
        /// </summary>
        private int IdRobotAuto { get; set; }
        /// <summary>
        /// Timer de producción
        /// </summary>
        public System.Timers.Timer TimerProduccion { get; set; }
        /// <summary>
        /// Lista de producciones del sistema
        /// </summary>
        public List<Produccion> ListaProducciones { 
            get
            {
                ServicioPersistencia= new Produccion();
                return ServicioPersistencia.MostrarTodoBD().Cast<Produccion>().ToList();
            }
            set {} 
        }
        /// <summary>
        /// Lista de producciones realizadas
        /// </summary>
        public List<HistoricoProduccion> ListaHistoricoProducciones
        {
            get
            {
                ServicioPersistencia = new HistoricoProduccion();
                return ServicioPersistencia.MostrarTodoBD().Cast<HistoricoProduccion>().ToList();                
            }
            set { }
        }
        /// <summary>
        /// Lista de Producciones a Producir
        /// </summary>
        private List<Produccion> ListaAProducir { get; set; }
        /// <summary>
        /// Delegado evento para loguear en pantalla la producción
        /// </summary>
        /// <param name="prod"></param>
        /// <param name="texto"></param>
        /// <returns></returns>
        public delegate void EventoDeLogueoEnPantallaProduccion(Produccion prod,string texto);
        /// <summary>
        /// Evento de logueo de producción en pantalla
        /// </summary>
        public event EventoDeLogueoEnPantallaProduccion EventoDeLogueoDeProduccion;
        /// <summary>
        /// Constructor sin parametros de forma privada para no permitir que se instancie el objeto.
        /// </summary>
        private Sistema() 
        {
            ListaDisenos = new List<Diseno>();
            ListaProducciones = new List<Produccion>();
            ListaObservadores = new List<IObservador>();            
            IdRobotAuto = 0;
            SegundosProduccion = 0;
            CantidadRobotAProducir = 0;
            ServicioPersistencia = new ObjetoPersistente();
        }
        /// <summary>
        /// Agrega un diseño
        /// </summary>
        /// <param name="diseno"></param>
        public bool AgregarDiseno(Diseno diseno)
        {
            if (!ExisteDiseno(diseno.Nombre))
            {
                if (diseno.ListaComponentes.Count() != 0)
                {
                    try
                    {
                        diseno.AgregarBD();
                    }
                    catch (BDExcepcion ex)
                    {
                        MessageBox.Show("Error al guardar Diseño en la base de datos - Mensaje: " + ex.Message);
                    }
                    ActualizarTodo(this);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Busca un diseño por nombre y retorna verdadero si lo encientra
        /// </summary>
        /// <param name="nombreDiseno"></param>
        /// <returns></returns>
        public bool ExisteDiseno(String nombreDiseno)
        {
            return ObtenerDiseno(nombreDiseno) != null;
        }

        public bool EsDisenoDeProduccion(Diseno diseno)
        {
            foreach (Produccion prod in ListaProducciones)
            {
                if (prod.DisenoProduccion.Equals(diseno))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Agrega un diseño al sistema y recibe sus popiedades por parametro
        /// </summary>
        /// <param name="nombre"></param>
        /// <param name="medioComponente"></param>
        public bool AgregarDiseno(String nombre, MedioComponente medioComponente,decimal costoAgregado, List<Componente> lstComponentes)
        {

            Diseno diseno = new Diseno(nombre,medioComponente,costoAgregado);
            diseno.ListaComponentes = lstComponentes;
            if (!ListaDisenos.Contains(diseno))
            {
                return this.AgregarDiseno(diseno);
            }
            return false;
        }
        /// <summary>
        /// Elimina un diseño
        /// </summary>
        /// <param name="diseno"></param>
        public void EliminarDiseno(Diseno diseno)
        {
            if (!EsDisenoDeProduccion(diseno))
            {
                diseno.EliminarBD();
            }
            else
            {
                throw new BDExcepcion("Diseno pertenece a una produccion");
            }
        }


         /// <summary>
        /// Obtengo un Diseno por su nombre
        /// </summary>
        /// <param name="nombre"></param>
        /// <returns></returns>
        public Diseno ObtenerDiseno(string nombre)
        {

            Diseno dis = null;
            if (ListaDisenos.Count() != 0)
            {
                try
                {
                    dis = (from Diseno d in ListaDisenos where d.Nombre.ToLower() == nombre.ToLower() select d).First();
                }
                catch
                {

                }
            }
            return dis;
        }

        /// <summary>
        /// Modifica el costo agregado y la lista de componentes de un diseno segun su nombre
        /// </summary>
        /// <param name="nombre"></param>
        /// <param name="componentes"></param>
        /// <returns></returns>
        public bool ModificarDiseno(String nombre,decimal costoAgregado,List<Componente> componentes)
        {
            Diseno diseno = ObtenerDiseno(nombre);
            diseno.CostoAgregado = costoAgregado;
            diseno.ListaComponentes = componentes;
            try
            {
                diseno.ModificarBD();
            }
            catch (BDExcepcion ex)
            {
                MessageBox.Show("Error al modificar Diseño en la base de datos - Mensaje: " + ex.Message);
            }
            ActualizarTodo(this);
            return true;
        }
        /// <summary>
        /// Agrega una produccion
        /// </summary>
        /// <param name="produccion"></param>
        public void AgregarProduccion(Diseno diseno,MedioComponente medio,int cantidadSolicitada,int cantidadProducida) 
        {
            Produccion produccion = new Produccion(diseno, medio, cantidadSolicitada, 0);
            try
            {
                produccion.AgregarBD();
            }
            catch (BDExcepcion ex)
            {
                MessageBox.Show("Error al guardar Producción en la base de datos - Mensaje: " + ex.Message);
            }            
        }
        /// <summary>
        /// Elimina una produccion
        /// </summary>
        /// <param name="produccion"></param>
        public void EliminarProduccion(int idProduccion)
        {
            Produccion prod = ObtenerProduccion(idProduccion);
            try
            {
                prod.EliminarBD();
            }
            catch (BDExcepcion ex)
            {
                MessageBox.Show("Error al eliminar Producción en la base de datos - Mensaje: " + ex.Message);
            }         
        }
        /// <summary>
        /// Obtiene la produccion con determinado idProducción
        /// </summary>
        /// <param name="idProduccion"></param>
        /// <returns></returns>
        public Produccion ObtenerProduccion(int idProduccion) 
        {
            Produccion prod;
            prod=(from Produccion p in ListaProducciones where p.Oid == idProduccion select p).First();
            return prod;
        }
        /// <summary>
        /// Produce un lista de robot
        /// </summary>
        /// <param name="producciones"></param>
        public void Producir(Produccion prod) 
        {     
            
            Componente componenteReal=null;

            if(prod.CantidadProducida==0)
                EventoLogueoProduccion(prod, "Inicial");
            prod.FechaInicioProduccion = DateTime.Now;
            prod.ModificarBD();

            //Creo el componente en el stock si es que no existe
            if (Stock.Instancia.ObtenerComponente(prod.DisenoProduccion.Nombre) == null)
            {
                Stock.Instancia.AgregarComponente(prod.DisenoProduccion.Nombre, "ROB-" + prod.DisenoProduccion.Nombre + IdRobotAuto.ToString("00000"), prod.DisenoProduccion.Medio, prod.DisenoProduccion.Costo, prod.DisenoProduccion.Descripcion, DateTime.Now, 0, prod.DisenoProduccion.FechaService, true, 0, 0, 0, 0, prod.DisenoProduccion.ListaComponentes);
                //Incremento autonumerado de idRobot cada vez que se crea uno nuevo
                IdRobotAuto++;
            }
            componenteReal = Stock.Instancia.ObtenerComponente(prod.DisenoProduccion.Nombre);

            for (int cant = 0; cant < this.CantidadRobotAProducir; cant++)
            {
                //Se produce siempre y cuando no supere su cantidad producida
                if (prod.CantidadProducida < prod.CantidadSolicitada)
                {
                    //Decremento la cantidad de los componentes en el stock
                    foreach (Componente comp in prod.DisenoProduccion.ListaComponentes)
                    {
                        Componente componente = Stock.Instancia.ObtenerComponente(comp.Modelo);
                        componente.Cantidad = componente.Cantidad - comp.Cantidad;
                        componente.ModificarBD();

                    }
                    //Subo la cantidad de su stock                             
                    componenteReal.Cantidad++;

                    //Aumento cantidad producida de la producción
                    prod.CantidadProducida++;
                    componenteReal.ModificarBD();
                    prod.ModificarBD();                   
                    Sistema.Instancia.ActualizarTodo(this);
                }
                else
                {
                    prod.FechaFinProduccion = DateTime.Now;
                    prod.ModificarBD();

                    HistoricoProduccion hist = new HistoricoProduccion(prod);
                    hist.AgregarBD();
                    //prod.GuardarHistoricco();
                    EventoLogueoProduccion(prod, "Final");
                    MessageBox.Show("Finalizo la Producción: " + prod.DisenoProduccion.Nombre + " Cantidad Producida: " + prod.CantidadProducida, "Información", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }
            }        
           
        }
        /// <summary>
        /// Crea timer para producción
        /// </summary>
        public void CrearTimerProduccion()
        {
            this.TimerProduccion = new System.Timers.Timer(this.SegundosProduccion * 1000);
            this.TimerProduccion.Elapsed += new System.Timers.ElapsedEventHandler(TimerProduccion_Elapsed);
            ListaAProducir = ListaProduccionesOrdenada();
            this.TimerProduccion.Start();
            this.TimerProduccion.Enabled = true;            
            GC.KeepAlive(this.TimerProduccion);
        }
        /// <summary>
        /// Evento que ejcuta el timer al finalizar el intervalo
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TimerProduccion_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {                               
                if (ListaAProducir.Count == 0)
                {
                    FinalizarProduccion();
                    Sistema.Instancia.ActualizarTodo(this);
                }
                else
                {
                    Produccion p = ListaAProducir.ElementAt(0);
                    if (p.CantidadRestante == 0)
                    {
                        ListaAProducir.RemoveAt(0);
                    }
                    Sistema.Instancia.Producir(p);
                }
                
            }
            catch (ProduccionExcepcion pex)
            {                
                Sistema.Instancia.TimerProduccion.Stop();
                MessageBox.Show(pex.Message, "Error al Producir", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
        }
        /// <summary>
        /// Lista ordenada de producciones por su idproduccion
        /// </summary>
        /// <returns></returns>
        public List<Produccion> ListaProduccionesOrdenada()
        {
            List<Produccion> list = (from Produccion p in ListaProducciones
                                     where p.FechaFinProduccion > DateTime.Now
                                     orderby p.DisenoProduccion.Medio ascending, p.FechaInicioProduccion ascending
                                     select p).ToList();
            return list;
        }

        /// <summary>
        /// Realiza un Stop del Timer para produccion
        /// </summary>
        public void FinalizarProduccion()
        {
            this.TimerProduccion.Stop();
        }

        /// <summary>
        /// Verificar que la producción pueda ser relizada con el stock que existe
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public bool ExisteStockParaProduccion()
        {
            foreach (Produccion p in ListaProduccionesOrdenada())
            {
                foreach (Componente comp in p.DisenoProduccion.ListaComponentes)
                {
                    int cantidadComp = comp.Cantidad * p.CantidadSolicitada;
                    Componente componente = Stock.Instancia.ObtenerComponente(comp.Modelo);
                    if (componente.Cantidad == 0 || (componente.Cantidad - cantidadComp) < 0)
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// Retorna si es necesario realizar un service a los componentes del diseno a Producir
        /// </summary>
        /// <returns></returns>
        public bool ExistenDisenosParaService()
        {            
            foreach (Produccion p in ListaProduccionesOrdenada())
            {
                if (p.DisenoProduccion.NecesitaService())
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Lista de Componentes que Necesitan Service
        /// </summary>
        /// <returns></returns>
        public List<Componente> ListaComponentesParaService()        
        {
            List<Componente> retorno = new List<Componente>();

            foreach (Componente comp in Stock.Instancia.ListaComponentes)
            {
                if (comp.NecesitaService())
                {
                    retorno.Add(comp);
                }
            }
            return retorno;
        }

        /// <summary>
        /// Realizar Service de una lista de componentes.
        /// </summary>
        /// <param name="componentes"></param>
        public void RealizarService(List<Componente> componentes)
        {
            if (componentes != null)
            {
                foreach (Componente comp in componentes)
                {
                    comp.RealizarService();
                }
            }
        }
        /// <summary>
        /// Se dispara el evento para loguear la producción en pantalla
        /// </summary>
        /// <param name="prod"></param>
        /// <param name="texto"></param>
        public void EventoLogueoProduccion(Produccion prod,string texto)
        {
            EventoDeLogueoDeProduccion(prod, texto);
        }
        /// <summary>
        /// Loguea stock de producciones
        /// </summary>
        /// <returns></returns>
        public string LogProduccion(Produccion prod,string textoDeEstadoDeProduccion)
        {
            string res=string.Empty;           
            
            res = "-----Stock "+textoDeEstadoDeProduccion+" Producción: "+prod.Oid+" ---------------------";
            res += Environment.NewLine;
            res += "Produccion: " + prod.Oid;
            res += Environment.NewLine;
            res += "Nombre Diseño: " + prod.DisenoProduccion.Nombre;
            res += Environment.NewLine;
            res += "Componentes---------------";
            res += Environment.NewLine;
            foreach (Componente comp in prod.DisenoProduccion.ListaComponentes)
            {
                Componente comp1 = Stock.Instancia.ObtenerComponente(comp.Modelo);
                res += "Componente: " + comp1.Modelo + " NumeroSerie: " + comp1.NumeroSerie + "Stock: " + comp1.Cantidad.ToString();
                res += Environment.NewLine;
            }
           
            return res;
        }

    }
}
